/*
 * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.xuexiang.xdlink.component.message.impl.base;

import android.support.annotation.NonNull;

import com.xuexiang.xdlink.component.message.IMessage;
import com.xuexiang.xdlink.util.ConvertTools;

/**
 * 基础消息体
 * 例子：
 * 55AA      |   Len   |   Func   |   CheckSum  |  Ret    | ...data...  |  00FF
 * 帧头 (固定) |   帧长   |  功能码   |   校验和     |  结果码  |     数据     | 帧尾(固定)
 *
 * @author xuexiang
 * @since 2018/8/30 下午11:15
 */
public class XMessage implements IMessage {

    /**
     * 最小数据长度为10
     */
    public static final int MIN_MESSAGE_LENGTH = 10;

    /**
     * 默认帧头
     */
    public static final byte[] DEFAULT_FRAME_HEAD = new byte[]{(byte) 0x55, (byte) 0xAA};
    /**
     * 默认帧尾
     */
    public static final byte[] DEFAULT_FRAME_END = new byte[]{(byte) 0x00, (byte) 0xFF};

    /**
     * 帧头(2个byte)
     * index:0
     */
    protected byte[] mFrameHead;
    /**
     * 帧长度(2个byte) -> short[-32768<---->32767]
     * index:2
     */
    private byte[] mFrameLength = new byte[2];
    /**
     * 功能码(一个byte)
     * index:4
     */
    private byte mFuncCode;
    /**
     * 是否需要校验
     */
    private boolean mIsCheck;
    /**
     * 校验和(二个byte)
     * index:5
     */
    private byte[] mCheckSum = new byte[2];
    /**
     * 结果码(一个byte)
     * index:7
     */
    private byte mRetCode;
    /**
     * 数据(不定长)
     * index:8
     */
    private byte[] mContentData;
    /**
     * 帧尾(2个byte)
     */
    protected byte[] mFrameEnd;

    /**
     * 构造方法
     */
    public XMessage() {
        this(false);
    }

    /**
     * 构造方法
     *
     * @param isCheck 是否需要校验和
     */
    public XMessage(boolean isCheck) {
        mIsCheck = isCheck;
    }

    @Override
    public byte[] toBytes() {
        short frameLength = calculateFrameLength(mContentData);
        ConvertTools.fillShortToBytesBigEndian(frameLength, mFrameLength, 0);

        if (mIsCheck) {
            short checkSum = calculateChecksum(mContentData);
            ConvertTools.fillShortToBytesBigEndian(checkSum, mCheckSum, 0);
        } else {
            mCheckSum = new byte[2]; //没有校验和就为0
        }

        byte[] messageBytes = new byte[frameLength + 4];  //帧头帧尾加起来长度为4
        System.arraycopy(mFrameHead, 0, messageBytes, 0, mFrameHead.length); //帧头
        System.arraycopy(mFrameLength, 0, messageBytes, 2, mFrameLength.length); //帧的长度
        messageBytes[4] = mFuncCode; //功能码

        System.arraycopy(mCheckSum, 0, messageBytes, 5, mCheckSum.length); //校验和
        messageBytes[7] = mRetCode; //结果码

        if (hasContentData()) {
            System.arraycopy(mContentData, 0, messageBytes, 8, mContentData.length); //填充数据
        }
        System.arraycopy(mFrameEnd, 0, messageBytes, messageBytes.length - 2, mFrameEnd.length);//帧尾
        return messageBytes;
    }

    /**
     * 填充消息体中的数据
     *
     * @param messageData 整个消息体
     */
    protected void fillMessageData(byte[] messageData) {
        mFrameHead = new byte[2];
        System.arraycopy(messageData, 0, mFrameHead, 0, mFrameHead.length);
        mFrameLength = new byte[2];
        System.arraycopy(messageData, 2, mFrameLength, 0, mFrameLength.length);

        mFuncCode = messageData[4];

        mCheckSum = new byte[2];
        System.arraycopy(messageData, 5, mCheckSum, 0, mCheckSum.length);

        mRetCode = messageData[7];

        mContentData = new byte[messageData.length - MIN_MESSAGE_LENGTH];
        if (mContentData.length > 0) {
            System.arraycopy(messageData, 8, mContentData, 0, mContentData.length);
        }

        mFrameEnd = new byte[2];
        System.arraycopy(messageData, messageData.length - 2, mFrameEnd, 0, mFrameEnd.length);
    }


    /**
     * @return 是否携带数据
     */
    private boolean hasContentData() {
        return mContentData != null && mContentData.length > 0;
    }

    /**
     * 计算帧的长度<br>
     * LEN =  (Len + Func + CheckSum + Ret) + DATA
     *
     * @param contentData 数据内容
     * @return 获取帧的长度
     */
    private short calculateFrameLength(byte[] contentData) {
        return (short) (contentData != null ? (contentData.length + 6) : 6);
    }

    /**
     * 计算校验和【简单地对ContentData内容进行相加】
     *
     * @param contentData 数据内容
     * @return
     */
    private short calculateChecksum(byte[] contentData) {
        if (contentData == null || contentData.length == 0) return 0;

        short checkSum = 0;
        for (byte dataItem : contentData) {
            checkSum += (short) ConvertTools.byteToIntUnSigned(dataItem);
        }
        return checkSum;
    }

    /**
     * @param messageData
     * @return
     */
    public boolean verifyMessage(byte[] messageData) {
        return verifyMessageLength(messageData) && verifyChecksum(messageData);
    }

    /**
     * 验证消息长度
     *
     * @param messageData 整个消息体
     * @return 消息长度是否正确
     */
    private static boolean verifyMessageLength(byte[] messageData) {
        //第三位是帧长度
        return messageData.length >= MIN_MESSAGE_LENGTH && (messageData.length - 4) == ConvertTools.bytesToShortBigEndian(messageData, 2);
    }

    /**
     * 检查校验和
     *
     * @param messageData 整个消息体
     * @return 校验和是否通过
     */
    private static boolean verifyChecksum(byte[] messageData) {
        if (messageData.length < MIN_MESSAGE_LENGTH) return false;

        short checkSum = ConvertTools.bytesToShortBigEndian(messageData, 5); //读取校验和
        //默认约定，读取到的校验和为0的话，视为不对校验和进行校验
        return checkSum == 0 || checkSum == calculatePackageMessageChecksum(messageData);
    }


    /**
     * 计算消息包的校验和
     *
     * @param messageData 整个消息体
     * @return 校验和
     */
    private static short calculatePackageMessageChecksum(@NonNull byte[] messageData) {
        if (messageData.length <= MIN_MESSAGE_LENGTH) return 0;

        short checkSum = 0;
        for (int index = 8; index < messageData.length - 2; index++) {
            checkSum += (short) ConvertTools.byteToIntUnSigned(messageData[index]);
        }
        return checkSum;
    }

    public byte[] getFrameHead() {
        return mFrameHead;
    }

    public XMessage setFrameHead(byte[] frameHead) {
        mFrameHead = frameHead;
        return this;
    }

    public short getFrameLength() {
        return calculateFrameLength(mContentData);
    }

    public byte getFuncCode() {
        return mFuncCode;
    }

    public XMessage setFuncCode(byte funcCode) {
        mFuncCode = funcCode;
        return this;
    }

    public boolean isCheck() {
        return mIsCheck;
    }

    public XMessage setIsCheck(boolean isCheck) {
        mIsCheck = isCheck;
        return this;
    }

    public byte[] getCheckSum() {
        return mCheckSum;
    }

    public XMessage setCheckSum(byte[] checkSum) {
        mCheckSum = checkSum;
        return this;
    }

    public byte getRetCode() {
        return mRetCode;
    }

    public XMessage setRetCode(byte retCode) {
        mRetCode = retCode;
        return this;
    }

    public byte[] getContentData() {
        return mContentData;
    }

    public XMessage setContentData(byte[] contentData) {
        mContentData = contentData;
        return this;
    }

    public byte[] getFrameEnd() {
        return mFrameEnd;
    }

    public XMessage setFrameEnd(byte[] frameEnd) {
        mFrameEnd = frameEnd;
        return this;
    }

}
